﻿Imports System
Imports System.Collections
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports System.ComponentModel.Design
Imports System.Globalization
Imports System.IO
Imports System.Reflection
Imports System.Runtime.CompilerServices
Imports System.Security.Cryptography
Imports System.Threading
Imports System.Workflow.ComponentModel
Imports System.Workflow.ComponentModel.Compiler
Imports System.Workflow.ComponentModel.Serialization
Imports System.Workflow.Runtime.Hosting
Imports System.Xml

Namespace System.Workflow.Runtime
    Friend NotInheritable Class WorkflowDefinitionDispenser
        Implements IDisposable
        ' Events
        Friend Custom Event WorkflowDefinitionLoaded As EventHandler([Of] WorkflowDefinitionEventArgs)

        ' Methods
        Friend Sub New(ByVal runtime As WorkflowRuntime, ByVal validateOnCreate As Boolean)
            Me.workflowRuntime = runtime
            Me.workflowTypes = New Dictionary([Of] Type, Activity)
            Me.xomlFragments = New Dictionary([Of] Byte(), Activity)(New DigestComparer)
            Me.workflowOutParameters = New Dictionary([Of] Type, List([Of] PropertyInfo))
            Me.parametersLock = New ReaderWriterLock
            Me.workflowFragmentsLock = New ReaderWriterLock
            Me.workflowTypesLock = New ReaderWriterLock
            Me.validateOnCreate = validateOnCreate
        End Sub

        Private Sub CacheOutputParameters(ByVal rootActivity As Activity)
            Dim key As Type = rootActivity.GetType
            Dim list As List([Of] PropertyInfo) = Nothing
            Me.parametersLock.AcquireWriterLock(-1)
            Try 
                If Not Me.workflowOutParameters.ContainsKey(key) Then
                    list = New List([Of] PropertyInfo)
                    Me.workflowOutParameters.Add(key, list)
                    Dim info As PropertyInfo
                    For Each info In key.GetProperties
                        If ((info.CanRead AndAlso (Not info.DeclaringType Is GetType(DependencyObject))) AndAlso ((Not info.DeclaringType Is GetType(Activity)) AndAlso (Not info.DeclaringType Is GetType(CompositeActivity)))) Then
                            Dim flag As Boolean = False
                            Dim property As DependencyProperty
                            For Each property In rootActivity.MetaDependencyProperties
                                If (([property].Name = info.Name) AndAlso [property].DefaultMetadata.IsMetaProperty) Then
                                    flag = True
                                    Exit For
                                End If
                            Next
                            If Not flag Then
                                list.Add(info)
                            End If
                        End If
                    Next
                End If
            Finally
                Thread.MemoryBarrier
                Me.parametersLock.ReleaseLock
            End Try
        End Sub

        Public Sub Dispose()
            Dim activity As Activity
            For Each activity In Me.workflowTypes.Values
                Try 
                    activity.Dispose
                    [Continue] For
                Catch exception1 As Exception
                    [Continue] For
                End Try
            Next
        End Sub

        Friend Function GetOutputParameters(ByVal rootActivity As Activity) As ReadOnlyCollection([Of] PropertyInfo)
            Dim key As Type = rootActivity.GetType
            Me.parametersLock.AcquireReaderLock(-1)
            Try 
                If Me.workflowOutParameters.ContainsKey(key) Then
                    Return New ReadOnlyCollection([Of] PropertyInfo)(Me.workflowOutParameters.Item(key))
                End If
            Finally
                Me.parametersLock.ReleaseLock
            End Try
            Me.CacheOutputParameters(rootActivity)
            Return Me.GetOutputParameters(rootActivity)
        End Function

        Friend Function GetRootActivity(ByVal workflowType As Type, ByVal createNew As Boolean, ByVal initForRuntime As Boolean) As Activity
            Dim activity As Activity = Nothing
            If createNew Then
                Return Me.LoadRootActivity(workflowType, False, initForRuntime)
            End If
            Me.workflowTypesLock.AcquireReaderLock(-1)
            Try 
                Me.workflowTypes.TryGetValue(workflowType, activity)
                If (Not activity Is Nothing) Then
                    Return activity
                End If
                Me.workflowTypesLock.UpgradeToWriterLock(-1)
                Me.workflowTypes.TryGetValue(workflowType, activity)
                If (Not activity Is Nothing) Then
                    Return activity
                End If
                activity = Me.LoadRootActivity(workflowType, True, initForRuntime)
                WorkflowDefinitionLock.SetWorkflowDefinitionLockObject(activity, New Object)
                Me.workflowTypes.Item(workflowType) = activity
            Finally
                Thread.MemoryBarrier
                Me.workflowTypesLock.ReleaseLock
            End Try
            Dim workflowDefinitionLoaded As EventHandler([Of] WorkflowDefinitionEventArgs) = Me.WorkflowDefinitionLoaded
            If (Not workflowDefinitionLoaded Is Nothing) Then
                workflowDefinitionLoaded.Invoke(Me.workflowRuntime, New WorkflowDefinitionEventArgs(workflowType))
            End If
            Return activity
        End Function

        Friend Function GetRootActivity(ByVal xomlText As String, ByVal rulesText As String, ByVal createNew As Boolean, ByVal initForRuntime As Boolean) As Activity
            If String.IsNullOrEmpty(xomlText) Then
                Throw New ArgumentNullException("xomlText")
            End If
            Dim xomlHashCode As Byte() = Nothing
            Dim md As MD5 = New MD5CryptoServiceProvider
            Dim stream As New MemoryStream
            [Using] writer As StreamWriter = New StreamWriter(stream)
                writer.Write(xomlText)
                If Not String.IsNullOrEmpty(rulesText) Then
                    writer.Write(rulesText)
                End If
                writer.Flush
                stream.Position = 0
                xomlHashCode = md.ComputeHash(stream)
            End [Using]
            If createNew Then
                Return Me.LoadRootActivity(xomlText, rulesText, xomlHashCode, False, initForRuntime)
            End If
            Dim activity As Activity = Nothing
            Me.workflowFragmentsLock.AcquireReaderLock(-1)
            Try 
                Me.xomlFragments.TryGetValue(xomlHashCode, activity)
                If (Not activity Is Nothing) Then
                    Return activity
                End If
                Me.workflowFragmentsLock.UpgradeToWriterLock(-1)
                Me.xomlFragments.TryGetValue(xomlHashCode, activity)
                If (Not activity Is Nothing) Then
                    Return activity
                End If
                activity = Me.LoadRootActivity(xomlText, rulesText, xomlHashCode, True, initForRuntime)
                WorkflowDefinitionLock.SetWorkflowDefinitionLockObject(activity, New Object)
                Me.xomlFragments.Item(xomlHashCode) = activity
            Finally
                Thread.MemoryBarrier
                Me.workflowFragmentsLock.ReleaseLock
            End Try
            Dim workflowDefinitionLoaded As EventHandler([Of] WorkflowDefinitionEventArgs) = Me.WorkflowDefinitionLoaded
            If (Not workflowDefinitionLoaded Is Nothing) Then
                workflowDefinitionLoaded.Invoke(Me.workflowRuntime, New WorkflowDefinitionEventArgs(xomlHashCode))
            End If
            Return activity
        End Function

        Friend Function GetWorkflowDefinition(ByVal workflowType As Type) As Activity
            If (workflowType Is Nothing) Then
                Throw New ArgumentNullException("workflowType")
            End If
            Return Me.GetRootActivity(workflowType, False, True)
        End Function

        Friend Function GetWorkflowDefinition(ByVal xomlHashCode As Byte()) As Activity
            Dim activity As Activity = Nothing
            If (xomlHashCode Is Nothing) Then
                Throw New ArgumentNullException("xomlHashCode")
            End If
            Me.workflowFragmentsLock.AcquireReaderLock(-1)
            Try 
                If Me.xomlFragments.ContainsKey(xomlHashCode) Then
                    activity = Me.xomlFragments.Item(xomlHashCode)
                End If
            Finally
                Me.workflowFragmentsLock.ReleaseLock
            End Try
            If (activity Is Nothing) Then
                Throw New ArgumentException("xomlHashCode")
            End If
            Return activity
        End Function

        Friend Function GetWorkflowDefinitions() As ReadOnlyCollection([Of] Byte())
            Dim onlys As ReadOnlyCollection([Of] Byte()) = Nothing
            Me.workflowFragmentsLock.AcquireReaderLock(-1)
            Try 
                onlys = New ReadOnlyCollection([Of] Byte())(New List([Of] Byte())(Me.xomlFragments.Keys))
            Finally
                Me.workflowFragmentsLock.ReleaseLock
            End Try
            Return onlys
        End Function

        Friend Function GetWorkflowTypes() As ReadOnlyCollection([Of] Type)
            Dim onlys As ReadOnlyCollection([Of] Type) = Nothing
            Me.workflowTypesLock.AcquireReaderLock(-1)
            Try 
                onlys = New ReadOnlyCollection([Of] Type)(New List([Of] Type)(Me.workflowTypes.Keys))
            Finally
                Me.workflowTypesLock.ReleaseLock
            End Try
            Return onlys
        End Function

        Private Function LoadRootActivity(ByVal workflowType As Type, ByVal createDefinition As Boolean, ByVal initForRuntime As Boolean) As Activity
            Dim root As Activity = Me.workflowRuntime.GetService([Of] WorkflowLoaderService).CreateInstance(workflowType)
            If (root Is Nothing) Then
                Throw New InvalidOperationException(ExecutionStringManager.CannotCreateRootActivity)
            End If
            If (Not root.GetType Is workflowType) Then
                Throw New InvalidOperationException(String.Format(CultureInfo.CurrentCulture, ExecutionStringManager.WorkflowTypeMismatch, New Object() { workflowType.FullName }))
            End If
            If createDefinition Then
                Me.ValidateDefinition(root, True, Me.workflowRuntime.GetService([Of] ITypeProvider))
            End If
            If initForRuntime Then
                DirectCast(root, IDependencyObjectAccessor).InitializeDefinitionForRuntime(Nothing)
            End If
            root.SetValue(Activity.WorkflowRuntimeProperty, Me.workflowRuntime)
            Return root
        End Function

        Private Function LoadRootActivity(ByVal xomlText As String, ByVal rulesText As String, ByVal xomlHashCode As Byte(), ByVal createDefinition As Boolean, ByVal initForRuntime As Boolean) As Activity
            Dim root As Activity = Nothing
            Dim service As WorkflowLoaderService = Me.workflowRuntime.GetService([Of] WorkflowLoaderService)
            [Using] reader As StringReader = New StringReader(xomlText)
                [Using] reader2 As XmlReader = XmlReader.Create(reader)
                    Dim rulesReader As XmlReader = Nothing
                    Dim input As StringReader = Nothing
                    Try 
                        If Not String.IsNullOrEmpty(rulesText) Then
                            input = New StringReader(rulesText)
                            rulesReader = XmlReader.Create(input)
                        End If
                        root = service.CreateInstance(reader2, rulesReader)
                    Finally
                        If (Not rulesReader Is Nothing) Then
                            rulesReader.Close
                        End If
                        If (Not input Is Nothing) Then
                            input.Close
                        End If
                    End Try
                End [Using]
            End [Using]
            If (root Is Nothing) Then
                Throw New InvalidOperationException(ExecutionStringManager.CannotCreateRootActivity)
            End If
            If createDefinition Then
                Dim typeProvider As ITypeProvider = Me.workflowRuntime.GetService([Of] ITypeProvider)
                Me.ValidateDefinition(root, False, typeProvider)
            End If
            If initForRuntime Then
                DirectCast(root, IDependencyObjectAccessor).InitializeDefinitionForRuntime(Nothing)
            End If
            root.SetValue(Activity.WorkflowXamlMarkupProperty, xomlText)
            root.SetValue(Activity.WorkflowRulesMarkupProperty, rulesText)
            root.SetValue(WorkflowDefinitionDispenser.WorkflowDefinitionHashCodeProperty, xomlHashCode)
            root.SetValue(Activity.WorkflowRuntimeProperty, Me.workflowRuntime)
            Return root
        End Function

        Friend Sub ValidateDefinition(ByVal root As Activity, ByVal isNewType As Boolean, ByVal typeProvider As ITypeProvider)
            If Me.validateOnCreate Then
                Dim errors As New ValidationErrorCollection
                If (typeProvider Is Nothing) Then
                    typeProvider = WorkflowRuntime.CreateTypeProvider(root)
                End If
                If Not isNewType Then
                    If Not String.IsNullOrEmpty([TryCast](root.GetValue(WorkflowMarkupSerializer.XClassProperty),String)) Then
                        errors.Add(New ValidationError(ExecutionStringManager.XomlWorkflowHasClassName, &H61C))
                    End If
                    Dim queue As New Queue
                    queue.Enqueue(root)
                    Do While (queue.Count > 0)
                        Dim activity As Activity = [TryCast](queue.Dequeue,Activity)
                        If (Not activity.GetValue(WorkflowMarkupSerializer.XCodeProperty) Is Nothing) Then
                            errors.Add(New ValidationError(ExecutionStringManager.XomlWorkflowHasCode, &H61D))
                        End If
                        Dim activity2 As CompositeActivity = [TryCast](activity,CompositeActivity)
                        If (Not activity2 Is Nothing) Then
                            Dim activity3 As Activity
                            For Each activity3 In activity2.EnabledActivities
                                queue.Enqueue(activity3)
                            Next
                        End If
                    Loop
                End If
                Dim serviceProvider As New ServiceContainer
                serviceProvider.AddService(GetType(ITypeProvider), typeProvider)
                Dim manager As New ValidationManager(serviceProvider)
                Dim validator As Validator
                For Each validator In manager.GetValidators(root.GetType)
                    Dim error As ValidationError
                    For Each error In validator.Validate(manager, root)
                        If Not [error].UserData.Contains(GetType(Activity)) Then
                            [error].UserData.Item(GetType(Activity)) = root
                        End If
                        errors.Add([error])
                    Next
                Next
                If errors.HasErrors Then
                    Throw New WorkflowValidationFailedException(ExecutionStringManager.WorkflowValidationFailure, errors)
                End If
            End If
        End Sub


        ' Fields
        Private parametersLock As ReaderWriterLock
        Private validateOnCreate As Boolean = True
        Friend Shared WorkflowDefinitionHashCodeProperty As DependencyProperty = DependencyProperty.RegisterAttached("WorkflowDefinitionHashCode", GetType(Byte()), GetType(WorkflowDefinitionDispenser))
        Private workflowFragmentsLock As ReaderWriterLock
        Private workflowOutParameters As Dictionary([Of] Type, List([Of] PropertyInfo))
        Private workflowRuntime As WorkflowRuntime
        Private workflowTypes As Dictionary([Of] Type, Activity)
        Private workflowTypesLock As ReaderWriterLock
        Private xomlFragments As Dictionary([Of] Byte(), Activity)
    End Class
End Namespace

